#pragma once

#include "Protocol.h"
#include <iostream>
#include <cstdio>
#include <string>
#include <cstdlib>
#include <cctype>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MESSAGE_BUFFER_SIZE 1024

namespace client {
    using namespace std;

    enum {
        USAGE_ERR = 1,
        SOCKET_ERR,
        CONNECT_ERR
    };

    class CalculatorClient {
    private:
        int _sockfd; // 与服务端通信用的套接字
        string _server_ip; // 服务端ip
        uint16_t _server_port; // 服务端端口

    public:
        CalculatorClient(const string& ip, const uint16_t& port)
            : _sockfd(-1)
            , _server_ip(ip)
            , _server_port(port)
        {}

        ~CalculatorClient() {
            if (_sockfd >= 0) close(_sockfd);
        }

    public:
        void initClient() {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0) {
                cerr << "client socket create err!" << endl;
                exit(SOCKET_ERR);
            }

            //* 客户端创建的socket也需要绑定，但我们不主动绑定，让OS在socket被使用时自己绑定
            //* 客户端TCP通信时不需要listen监听也不需要accept接收连接请求
            //* 客户端要做的就是直接对服务端发起连接请求
        }

        void start() {
            struct sockaddr_in server;
            bzero(&server, sizeof(server));

            server.sin_family = AF_INET;
            server.sin_port = htons(_server_port);
            server.sin_addr.s_addr = inet_addr(_server_ip.c_str());

            if (connect(_sockfd, (struct sockaddr*)&server, sizeof(server)) != 0) {
                cerr << "socket build connection err!" << endl;
                exit(CONNECT_ERR);
            }
            else {
                std::string cmd_line;
                while (true) {
                    //task 从键盘中获取用户输入
                    std::cout << "[WebCalculator]Please enter# ";
                    std::getline(std::cin, cmd_line);

                    //task 构建request
                    Request request;
                    if (!parseToReQuest(cmd_line, &request)) {
                        continue;
                    }

                    //task 序列化request
                    std::string request_text;
                    request.serialize(&request_text);

                    //task 给有效载荷附上报头
                    std::string request_package(addHeader(request_text));

                    //task 发送包文
                    send(_sockfd, request_package.c_str(), request_package.size(), 0);

                    //task 接收服务端发来的response包
                    std::string response_package;
                    receivePackage(_sockfd, &response_package);

                    //task 去掉response包的报头
                    std::string response_text(removeHeader(response_package));

                    //task 反序列化得到response对象
                    Response response;
                    response.unserialize(response_text);

                    //task 输出服务端计算出的答案
                    std::cout << "exitcode: " << response._exitcode << std::endl;
                    std::cout << "result: " << response._result << std::endl;
                }
            }
        } //@end void start()

    private:
        /**
         * 将用户在命令行上输入的命令转化为request
        */
        bool parseToReQuest(const std::string& cmd_line, Request* request) {
            int status = 0; // 0: 在操作符前 1: 操作符 2: 在操作符后
            int size = (int)cmd_line.size();
            std::string left, right;
            char oper;

            // 简易版本的状态机
            int i = 0;
            while (i < size) {
                switch (status) {
                case 0: 
                    if (isdigit(cmd_line[i])) {
                        left += cmd_line[i++];
                    }
                    else {
                        oper = cmd_line[i];
                        status = 1;
                    }
                    
                    break;

                case 1: 
                    status = 2;
                    ++i;
                
                    break;

                case 2: 
                    right += cmd_line[i++];
                    break;

                default:
                    std::cout << "输入不符合规范， 请重新输入" << std::endl;
                    return false;
                }
            } //@end while (i < size)

                *request = Request(std::stoi(left), std::stoi(right), oper);
                return true;
            
        } //@end Request parseToReQuest(const std::string& cmd_line)

    }; //@end class TcpClient
} //@end namespace client